package org.xx.armory.spring5;

import org.springframework.core.io.Resource;
import org.xx.armory.commons.KeyUtils;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;

import static org.xx.armory.commons.Validators.rejectIf;
import static org.xx.armory.commons.Validators.rejectIfNonPositive;
import static org.xx.armory.commons.Validators.rejectIfNull;

/**
 * 资源相关的工具类。
 */
public final class ResourceUtils {
    private ResourceUtils() {
        throw new AssertionError();
    }

    public static byte[] readBytes(
            Resource resource,
            int size
    )
            throws IOException {
        rejectIfNull(resource, "resource");
        rejectIf(() -> !resource.isReadable(), resource.getURI() + " not readable", "resource");
        rejectIfNonPositive(size, "size");

        int l = (int) resource.contentLength();
        if (l > size) {
            l = size;
        }

        try (final var channel = resource.readableChannel()) {
            final var buffer = ByteBuffer.allocate(l);
            final var n = channel.read(buffer);
            final var result = new byte[n];
            buffer.flip();
            buffer.get(result);
            return result;
        }
    }

    public static String readString(
            Resource resource,
            int size,
            Charset charset
    )
            throws IOException {
        rejectIfNull(resource, "resource");
        rejectIf(() -> !resource.isReadable(), resource.getURI() + " not readable", "resource");
        rejectIfNonPositive(size, "size");
        rejectIfNull(charset, "charset");

        int l = (int) resource.contentLength();
        if (l > size) {
            l = size;
        }

        try (final var channel = resource.readableChannel()) {
            final var buffer = ByteBuffer.allocate(l);
            channel.read(buffer);
            buffer.flip();
            return charset.decode(buffer).toString();
        }
    }

    /**
     * 从PEM格式的资源文件中读取私钥。
     *
     * @param resource
     *         包含PEM格式的私钥的资源。
     * @return 已读取的私钥。
     * @throws IllegalArgumentException
     *         如果参数{@code resource}是{@code null}或者不可读。
     * @throws UncheckedIOException
     *         如果无法打开资源文件并读取内容，如果资源文件的内容不是合法的PEM格式，如果PEM文件中的私钥被加密。
     */
    public static PrivateKey loadPrivateKeyFromPEM(
            Resource resource
    ) {
        rejectIfNull(resource, "resource");
        try {
            rejectIf(() -> !resource.isReadable(), resource.getURI() + " not readable", "resource");
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }

        try (final var stream = resource.getInputStream()) {
            return KeyUtils.loadPrivateKeyFromPEM(stream);
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    /**
     * 从PEM格式的资源文件中读取公钥。
     *
     * @param resource
     *         包含PEM格式的公钥的资源。
     * @return 已读取的公钥。
     * @throws IllegalArgumentException
     *         如果参数{@code resource}是{@code null}或者不可读。
     * @throws UncheckedIOException
     *         如果无法打开资源文件并读取内容，如果资源文件的内容不是合法的PEM格式。
     */
    public static PublicKey loadPublicKeyFromPEM(
            Resource resource
    ) {
        rejectIfNull(resource, "resource");
        try {
            rejectIf(() -> !resource.isReadable(), resource.getURI() + " not readable", "resource");
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }

        try (final var stream = resource.getInputStream()) {
            return KeyUtils.loadPublicKeyFromPEM(stream);
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    /**
     * 从PEM格式的资源文件中读取证书。
     *
     * @param resource
     *         包含PEM格式的证书的资源。
     * @return 已读取的证书。
     * @throws IllegalArgumentException
     *         如果参数{@code resource}是{@code null}或者不可读。
     * @throws IllegalStateException
     *         如果当前jvm无法解析x.509格式的证书。
     * @throws UncheckedIOException
     *         如果无法打开资源文件并读取内容，如果资源文件的内容不是合法的PEM格式。
     */
    public static Certificate loadCertificateFromPEM(
            Resource resource
    ) {
        rejectIfNull(resource, "resource");
        try {
            rejectIf(() -> !resource.isReadable(), resource.getURI() + " not readable", "resource");
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }

        try (final var stream = resource.getInputStream()) {
            return KeyUtils.loadCertificateFromPEM(stream);
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }
}
